package com.michaelbulava.TFS.vcs.internal.providers.internal;

import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.util.diff.Diff;
import com.intellij.util.diff.FilesTooBigForDiffException;
import com.michaelbulava.TFS.vcs.internal.TFSFileRevision;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Created by MichaelA on 8/18/2015.
 */
public class AnnotationBuilder {
    private final String myAnnotatedContent;
    private final VcsFileRevision[] myLineRevisions;
    private final List<Integer> myLineNumbers;

    public AnnotationBuilder(List<TFSFileRevision> revisions, ContentProvider contentProvider) throws VcsException {
        if ((revisions == null) || (revisions.size() < 1)) {
            throw new IllegalArgumentException();
        }
        Iterator<TFSFileRevision> iterator = revisions.iterator();
        TFSFileRevision revision = (TFSFileRevision)iterator.next();
        this.myAnnotatedContent = contentProvider.getContent(revision);
        String[] lines = splitLines(this.myAnnotatedContent);

        this.myLineRevisions = new VcsFileRevision[lines.length];
        this.myLineNumbers = new ArrayList(lines.length);
        for (int i = 0; i < lines.length; i++) {
            this.myLineNumbers.add(Integer.valueOf(i));
        }
        while (iterator.hasNext()) {
            TFSFileRevision previousRevision = (TFSFileRevision)iterator.next();
            String previousContent = contentProvider.getContent(previousRevision);
            String[] previousLines = splitLines(previousContent);
            Diff.Change change;
            try {
                change = Diff.buildChanges(previousLines, lines);
            } catch (FilesTooBigForDiffException e) {
                throw new VcsException(e);
            }
            annotateAll(change, revision);
            if (allLinesAnnotated()) {
                break;
            }
            lines = previousLines;
            revision = previousRevision;
        }
        fillAllNotAnnotated((VcsFileRevision)revisions.get(revisions.size() - 1));
    }

    private void annotateAll(Diff.Change changesList, VcsFileRevision revision) {
        Diff.Change change = changesList;
        while (change != null) {
            annotate(change, revision);
            change = change.link;
        }
        recalculateLineNumbers(changesList);
    }

    private void annotate(Diff.Change change, VcsFileRevision revision) {
        if (change.inserted > 0) {
            for (int line = change.line1; line < change.line1 + change.inserted; line++) {
                Integer origLine = (Integer)this.myLineNumbers.get(line);
                if ((origLine != null) &&
                        (this.myLineRevisions[origLine.intValue()] == null)) {
                    this.myLineRevisions[origLine.intValue()] = revision;
                }
            }
        }
    }

    private void recalculateLineNumbers(Diff.Change changesList) {
        Diff.Change change = changesList;
        int removedLinesCount = 0;
        while (change != null) {
            for (int i = 0; i < change.inserted; i++) {
                this.myLineNumbers.remove(change.line1 - removedLinesCount);
            }
            removedLinesCount += change.inserted;
            change = change.link;
        }
        change = changesList;
        while (change != null) {
            for (int i = 0; i < change.deleted; i++) {
                this.myLineNumbers.add(change.line0, null);
            }
            change = change.link;
        }
    }

    private boolean allLinesAnnotated() {
        for (VcsFileRevision revision : this.myLineRevisions) {
            if (revision == null) {
                return false;
            }
        }
        return true;
    }

    private void fillAllNotAnnotated(VcsFileRevision vcsFileRevision) {
        for (int i = 0; i < this.myLineRevisions.length; i++) {
            if (this.myLineRevisions[i] == null) {
                this.myLineRevisions[i] = vcsFileRevision;
            }
        }
    }

    private static String[] splitLines(String string)
    {
        string = StreamUtil.convertSeparators(string);

        boolean spaceAdded = false;
        if (string.endsWith("\n")) {
            string = string + " ";
            spaceAdded = true;
        }
        String[] temp = string.split("\n");
        if (spaceAdded) {
            String[] result = new String[temp.length - 1];
            System.arraycopy(temp, 0, result, 0, result.length);
            return result;
        }
        return temp;
    }

    public String getAnnotatedContent()
    {
        return this.myAnnotatedContent;
    }

    public VcsFileRevision[] getLineRevisions()
    {
        return this.myLineRevisions;
    }

    public static abstract interface ContentProvider
    {
        public abstract String getContent(TFSFileRevision paramTFSFileRevision)
                throws VcsException;
    }
}
